﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace sqlDALgen.Renderers
{
    public class LoopRenderer : IRenderer
    {
        private readonly String _dataPath;
        private readonly String _cut;
        private readonly String _where;
        private readonly String _equals;
        private readonly String _notEquals;
        private readonly String _contains;
        private int _start;
        private int _end;

        /// <summary>
        /// Checks if the loop where condition is matched
        /// accepts: data
        /// returns bool
        /// </summary>
        private readonly Func<TemplateData, bool> _wherePredicate;

        /// <summary>
        /// Checks if the loop counter is in range
        /// accepts: loop counter, data
        /// returns bool
        /// </summary>
        private readonly Func<int, TemplateData, bool> _rangePredicate;

        private readonly XmlNode _node;

        public LoopRenderer(XmlNode node)
        {
            _node = node;
            _dataPath = node.Attributes["for"].Value.ToString();
            _cut = node.Attributes["cut"] == null ? String.Empty : node.Attributes["cut"].Value.ToString();
            _where = node.Attributes["where"] == null ? String.Empty : node.Attributes["where"].Value.ToString();
            _equals = node.Attributes["equals"] == null ? String.Empty : node.Attributes["equals"].Value.ToString();
            _notEquals = node.Attributes["notEquals"] == null ? String.Empty : node.Attributes["notEquals"].Value.ToString();
            _contains = node.Attributes["contains"] == null ? String.Empty : node.Attributes["contains"].Value.ToString();

            #region Where Predicate
            if (!String.IsNullOrEmpty(_where))
            {
                if (!String.IsNullOrEmpty(_equals))
                {
                    _wherePredicate = (data) =>
                    {
                        var whereVal = data.ResolvePath(_where.Substring(1, _where.Length - 2)).ToString();
                        var equalVal = data.ResolvePath(_equals.Substring(1, _equals.Length - 2)).ToString();
                        return whereVal.Equals(equalVal, StringComparison.OrdinalIgnoreCase);
                    };
                }
                else if (!String.IsNullOrEmpty(_notEquals))
                {
                    _wherePredicate = (data) =>
                    {
                        var whereVal = data.ResolvePath(_where.Substring(1, _where.Length - 2)).ToString();
                        var equalVal = data.ResolvePath(_equals.Substring(1, _equals.Length - 2)).ToString();
                        return !whereVal.Equals(equalVal, StringComparison.OrdinalIgnoreCase);
                    };
                }
                else
                {
                    _wherePredicate = (data) =>
                    {
                        var whereVal = data.ResolvePath(_where.Substring(1, _where.Length - 2)).ToString();
                        var equalVal = data.ResolvePath(_equals.Substring(1, _equals.Length - 2)).ToString();
                        return whereVal.Contains(equalVal);
                    };
                }
            }
            else
                _wherePredicate = (data) => true;
            #endregion

            #region Range Predicate
            var range = node.Attributes["range"] == null ? String.Empty : node.Attributes["range"].Value.ToString();
            if (String.IsNullOrEmpty(range))
                _rangePredicate = (i, d) => true;
            else
            {
                var cnt = 0;
                foreach (var r in range.Split(new char[] { ':' }, 2))
                {
                    if (cnt == 0) _start = int.Parse(r);
                    else _end = int.Parse(r);

                    cnt++;
                }

                _rangePredicate = (i, d) =>
                {

                    if (_end > 0) return i >= _start && i <= _end;
                    return i >= _start && i <= d.Keys.Count + _end;
                };
            }
            #endregion
        }

        public String Render(TemplateData data)
        {
            var builder = new StringBuilder();
            var sourceData = data;

            data = data.ResolvePath(_dataPath.Substring(1, _dataPath.Length - 2));

            var cnt = 0;
            foreach (var key in data)
            {
                data = new TemplateData { 
                            { "item", key.Value },
                            { "ctx", sourceData }
                        };

                if (!_rangePredicate(cnt++, data)
                        || !_wherePredicate(data)) continue;

                foreach (XmlNode node in _node.ChildNodes)
                {
                    builder.Append(TemplateRenderer.Get(node).Render(data));
                }
            }

            if (String.IsNullOrEmpty(_cut))
                return builder.ToString();
            else
                return builder.Remove(builder.Length + Int32.Parse(_cut), Math.Abs(Int32.Parse(_cut))).ToString();
        }

        public static IRenderer Get(XmlNode node)
        {
            return new LoopRenderer(node);
        }

        public string FileName
        {
            get { throw new NotImplementedException(); }
        }
    }
}
